package com.sh.service.store.impl;

import com.aliyun.oss.OSS;
import com.aliyun.oss.model.ListObjectsRequest;
import com.aliyun.oss.model.ObjectListing;
import com.sh.common.exception.BizException;
import com.sh.config.OssClientConfig;
import com.sh.entity.Bucket;
import com.sh.entity.ObjectItem;
import com.sh.enums.OssTypeEnum;
import com.sh.enums.PermissionTypeEnum;
import com.sh.pojo.bo.CopyObjectsBO;
import com.sh.pojo.dto.ResourceTreeDTO;
import com.sh.pojo.vo.DownLoadObjectVO;
import com.sh.service.store.IOSService;
import com.sh.utils.CommonUtils;
import com.sh.utils.PathUtil;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.BindException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Slf4j
public class MinioService implements IOSService {

    @Autowired
    private OssClientConfig ossClientConfig;
    private MinioClient minioClient;

                public MinioService(OssClientConfig ossClientConfig) {
        this.ossClientConfig = ossClientConfig;
        if (OssTypeEnum.MINIO.getName().equals(ossClientConfig.getResourceType())) {
            minioClient = MinioClient.builder()
                    .endpoint(ossClientConfig.getEndpoint())
                    .credentials(ossClientConfig.getAccessKey(), ossClientConfig.getSecretKey())
                    .build();
        }
    }

    @Override
    public List<Bucket> listBuckets() {
        List<Bucket> buckets = new ArrayList<>();
        try {
            List<io.minio.messages.Bucket> bs = minioClient.listBuckets();
            bs.forEach(b -> {
                Bucket bucket = new Bucket();
                bucket.setName(b.name());
                buckets.add(bucket);
            });
        } catch (Exception e) {
            log.error("list buckets error", e);
        }
        return buckets;
    }

    @Override
    public List<String> listAllObjects(String bucket, String objectPath) {
        List<String> objectKeys = new ArrayList<>();
        ListObjectsArgs args = ListObjectsArgs.builder().bucket(bucket).prefix(objectPath).build();
        Iterable<Result<Item>> results = minioClient.listObjects(args);
        results.forEach(itemResult -> {
            Item item = null;
            try {
                item = itemResult.get();
            } catch (Exception e) {
                log.error("list objects error", e);
            }
            if (item != null) {
                objectKeys.add(item.objectName());
            }
        });
        return objectKeys;
    }

    @Override
    public List<ObjectItem> listObjects(String bucketName, String prefix) throws BizException {
        return listObjects(bucketName, prefix, "");
    }

    @Override
    public List<ObjectItem> listObjects(String bucketName, String prefix, String keyword) throws BizException {
        if (StringUtils.isEmpty(bucketName)) {
            bucketName = ossClientConfig.getBucket();
        }
        if (StringUtils.isEmpty(keyword)) {
            keyword = "";
        }
        List<ObjectItem> objects = new ArrayList<>();
        try {
            Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder()
                    .bucket(bucketName)
                    .prefix(prefix + keyword)
                    .build());
            results.forEach(itemResult -> {
                Item item = null;
                try {
                    item = itemResult.get();
                } catch (Exception e) {
                    log.error("list objects error", e);
                }
                if (item != null) {
                    ObjectItem objectItem = ObjectItem.builder()
                            .objectName(item.objectName())
                            .size(item.size())
                            .isDir(item.isDir())
                            .build();
                    objects.add(objectItem);
                }
            });
        } catch (Exception e) {
            log.error("list objects error", e);
            throw new BizException("查询对象失败："+e.getMessage());
        }
        return objects;
    }

    @Override
    public InputStream getObject(String bucketName, String prefix) throws BizException {
        if (StringUtils.isEmpty(bucketName)) {
            bucketName = ossClientConfig.getBucket();
        }
        try {
            return minioClient.getObject(GetObjectArgs.builder()
                    .bucket(bucketName)
                    .object(prefix)
                    .build());
        } catch (Exception e) {
            log.error("get object error", e);
            throw new BizException("下载对象流失败："+e.getMessage());
        }
    }

    @Override
    public String signUrl(String bucketName, String objectName, Integer expires) {
        if (StringUtils.isEmpty(bucketName)) {
            bucketName = ossClientConfig.getBucket();
        }
        if (expires == null) {
            expires = ossClientConfig.getSignExpires();
        }
        try {
            return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .method(Method.GET)
                    .expiry(expires, TimeUnit.DAYS)
                    .build());
        } catch (Exception e) {
            log.error("url signed error", e);
        }
        return null;
    }

    @Override
    public void putObject(String bucketName, String objectName, MultipartFile file) throws BizException {
        try (InputStream is = file.getInputStream()) {
            PutObjectArgs args = PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .stream(is, file.getSize(), -1)
                    .build();
            minioClient.putObject(args);
        } catch (Exception e) {
            log.error("put object error", e);
            throw new BizException("上传对象失败："+e.getMessage());
        }
    }

    @Override
    public List<String> getFirstFolder(String bucketName) throws BizException {
        ListObjectsArgs listObjectsArgs = ListObjectsArgs.builder().bucket(bucketName).prefix("").recursive(false).delimiter("/").build();
        try {
            List<String> firstFolder = new ArrayList<>();
            // 列出指定存储桶的下一级路径
            Iterable<Result<Item>> results = minioClient.listObjects(listObjectsArgs);

            // 输出下一级路径或对象
            for (Result<Item> result : results) {
                Item item = result.get();
                if (item.isDir()) {
                    firstFolder.add(item.objectName());
                }
            }
            return firstFolder;
        } catch (Exception e) {
            log.error("查询初始路径失败：{}", e);
            throw new BizException("查询初始路径失败：" + e.getMessage());
        }
    }

    @Override
    public List<ResourceTreeDTO> listDirectories(String bucketName, String prefix, int currentDepth, int maxDepth) {
        List<ResourceTreeDTO> treeNodes = new ArrayList<>();

        // 如果是根目录（桶的起点），添加桶节点
        if (currentDepth == 0 && (prefix == null || prefix.isEmpty())) {
            ResourceTreeDTO bucketNode = new ResourceTreeDTO();
            bucketNode.setName(bucketName);  // 桶名作为根节点名
            bucketNode.setLevel(currentDepth);  // 根节点的层级为0
            bucketNode.setPermission(PermissionTypeEnum.NONE.getCode());  // 桶的初始权限
            treeNodes.add(bucketNode);

            // 递归进入下一级目录
            List<ResourceTreeDTO> children = listDirectories(bucketName, "", currentDepth + 1, maxDepth);
            bucketNode.setChildren(children);

            return treeNodes;  // 返回包含桶节点的完整树
        }

        // 如果到达最大深度，则停止递归
        if (currentDepth >= maxDepth) {
            return treeNodes;
        }

        try {
            // 列出对象
            Iterable<Result<Item>> results = minioClient.listObjects(
                    ListObjectsArgs.builder()
                            .bucket(bucketName)
                            .prefix(prefix)
                            .recursive(false) // 只获取当前层级
                            .build()
            );

            // 列出目录 (CommonPrefixes)
            List<String> commonPrefixes = new ArrayList<>();
            for (Result<Item> result : results) {
                String objectName = result.get().objectName();
                if (objectName.endsWith("/")) {
                    commonPrefixes.add(objectName);
                }
            }

            for (String commonPrefix : commonPrefixes) {
                // 创建树节点
                ResourceTreeDTO treeNode = new ResourceTreeDTO();
                treeNode.setName(CommonUtils.getLastDirectoryName(commonPrefix));
                treeNode.setLevel(currentDepth);
                treeNode.setPermission(PermissionTypeEnum.NONE.getCode());
                treeNode.setPath(commonPrefix);

                // 递归调用，处理子目录
                List<ResourceTreeDTO> children = listDirectories(bucketName, commonPrefix, currentDepth + 1, maxDepth);
                treeNode.setChildren(children);

                // 添加到当前层级的树节点列表
                treeNodes.add(treeNode);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        return treeNodes;
    }


    @Override
    public void createFolder(String bucket, String path) throws BizException {
        try {
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucket)
                            .object(path)
                            .stream(new ByteArrayInputStream(new byte[0]), 0, -1)
                            .build()
            );
        } catch (Exception e) {
            log.error("创建MINIO文件夹失败：{}", e);
            throw new BizException("创建文件夹失败：" + e.getMessage());
        }

    }

    @Override
    public void deleteObject(String bucket, String objectName) throws BizException {
        try {
            minioClient.removeObject(
                    RemoveObjectArgs.builder()
                            .bucket(bucket)
                            .object(objectName)
                            .build()
            );
        } catch (Exception e) {
            log.error("delete minio object err:{}", e);
            throw new BizException("删除对象失败：" + e.getMessage());
        }
    }

    @Override
    public void deleteFolder(String bucket, String folderPath) throws BizException {
        try {
            // 列出文件夹中的所有对象
            Iterable<Result<Item>> results = minioClient.listObjects(
                    ListObjectsArgs.builder()
                            .bucket(bucket)
                            .prefix(folderPath)
                            .recursive(true)  // 递归获取文件夹中的所有对象
                            .build()
            );

            List<String> objectsToDelete = new ArrayList<>();
            for (Result<Item> result : results) {
                String objectName = result.get().objectName();
                objectsToDelete.add(objectName);
            }

            // 删除所有对象
            for (String objectName : objectsToDelete) {
                minioClient.removeObject(RemoveObjectArgs.builder()
                        .bucket(bucket)
                        .object(objectName)
                        .build());
            }

        } catch (Exception e) {
            log.error("delete folder err:{}", e);
            throw new BizException("删除文件夹失败：" + e.getMessage());
        }
    }

    @Override
    public void deleteObjects(String bucket, List<String> objectKeys) throws BizException {
        try {
            List<DeleteObject> deleteObjects = new ArrayList<>();
            for (String objectName : objectKeys) {
                deleteObjects.add(new DeleteObject(objectName));
            }

            // 批量删除对象
            minioClient.removeObjects(RemoveObjectsArgs.builder()
                    .bucket(bucket)
                    .objects(deleteObjects)
                    .build());
        } catch (Exception e) {
            log.error("delete objects err:{}", e);
            throw new BizException("批量删除对象失败：" + e.getMessage());
        }
    }

    @Override
    public void renameObject(String bucket, String originalObjectKey, String newObjectKey) throws BizException {
        try {
            // 复制对象到新位置
            minioClient.copyObject(CopyObjectArgs.builder()
                    .bucket(bucket)
                    .object(newObjectKey)
                    .source(CopySource.builder().bucket(bucket).bucket(originalObjectKey).build()) // 源对象的完整路径
                    .build());

            // 删除原对象
            minioClient.removeObject(RemoveObjectArgs.builder()
                    .bucket(bucket)
                    .object(originalObjectKey)
                    .build());
        } catch (Exception e) {
            log.error("rename object err:{}", e);
            throw new BizException("重命名对象失败：" + e.getMessage());
        }
    }

    @Override
    public void renameFolder(String bucket, String oldFolder, String newFolder) throws BizException {
        try {
            // 列出原文件夹中的所有对象
            Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder()
                    .bucket(bucket)
                    .prefix(oldFolder)
                    .recursive(true)  // 递归获取文件夹中的所有对象
                    .build());

            List<String> objectsToDelete = new ArrayList<>();

            // 遍历并重命名对象
            for (Result<Item> result : results) {
                String oldObjectName = result.get().objectName();
                String newObjectName = oldObjectName.replace(oldFolder, newFolder);

                // 复制对象到新位置
                minioClient.copyObject(CopyObjectArgs.builder()
                        .bucket(bucket)
                        .object(newObjectName)
                        .source(CopySource.builder()
                                .bucket(bucket)
                                .object(oldObjectName)
                                .build())
                        .build());

                // 记录要删除的旧对象
                objectsToDelete.add(oldObjectName);
            }

            // 删除原对象
            for (String oldObjectName : objectsToDelete) {
                minioClient.removeObject(RemoveObjectArgs.builder()
                        .bucket(bucket)
                        .object(oldObjectName)
                        .build());
            }
        } catch (Exception e) {
            log.error("rename folder err:{}", e);
            throw new BizException("重命名文件夹失败：" + e.getMessage());
        }
    }

    @Override
    public void copyObject(String bucket, String originalObjectKey, String newObjectKey) throws BizException {
        try {
            CopyObjectArgs args = CopyObjectArgs.builder().bucket(bucket).object(newObjectKey).source(CopySource.builder().bucket(bucket).object(originalObjectKey).build()).build();
            minioClient.copyObject(args);
        } catch (Exception e) {
            log.error("copy object err:{}", e);
            throw new BizException("复制对象失败：" + e.getMessage());
        }
    }

    @Override
    public void copyObjects(CopyObjectsBO bo) {

    }

    @Override
    public List<DownLoadObjectVO> signUrls(String bucket, List<String> objectKeys, Integer expirationInMillis, String localPath) {
        List<DownLoadObjectVO> urls = new ArrayList<>();
        if (objectKeys == null || objectKeys.isEmpty()) {
            return urls;
        }
        if (expirationInMillis == null) {
            expirationInMillis = ossClientConfig.getSignExpires();
        }

        String parentFolder = PathUtil.findLongestCommonPath(objectKeys);
        for (String objectKey : objectKeys) {
            DownLoadObjectVO vo = new DownLoadObjectVO();
            if (objectKey.endsWith("/")) {
                vo.setIsDir(true);
            } else {
                String downloadUrl = signUrl(bucket, objectKey, expirationInMillis);
                vo.setDownloadUrl(downloadUrl);
                vo.setIsDir(false);
            }
            vo.setLocalPath(Paths.get(localPath, CommonUtils.removeKeyword(objectKey, parentFolder)).normalize().toString());
            urls.add(vo);
        }
        return urls;
    }

    @Override
    public List<DownLoadObjectVO> downloadObjects(String bucket, List<String> objectsPath, Integer expire, String downloadPath) throws BizException {
        List<DownLoadObjectVO> urls = new ArrayList<>();

        if (objectsPath == null || objectsPath.isEmpty()) {
            return urls;
        }

        List<String> objectKeys = new ArrayList<>();

        // 根据是否以 '/' 结尾来分组
        Map<Boolean, List<String>> partitioned = objectsPath.stream()
                .collect(Collectors.partitioningBy(s -> s.endsWith("/")));

        objectKeys.addAll(partitioned.get(false));

        List<String> folderList = partitioned.get(true);
        if (folderList != null && !folderList.isEmpty()) {
            for (String folder : folderList) {
                List<String> objects = listAllObjects(bucket, folder);
                objectKeys.addAll(objects);
            }
        }

        // 下载对象到本地
        if (objectKeys.isEmpty()) {
            return urls;
        }

        // 下载每个对象
        for (String objectKey : objectKeys) {
            // 下载对象到指定的本地路径
            try {
                String localFilePath = Paths.get(downloadPath, objectKey).toString();
                File file = new File(localFilePath);
                file.getParentFile().mkdirs(); // 创建父目录

                // 使用 MinIO SDK 下载对象
                minioClient.getObject(GetObjectArgs.builder()
                        .bucket(bucket)
                        .object(objectKey)
                        .build());

                // 生成下载的签名 URL
                DownLoadObjectVO vo = new DownLoadObjectVO();
                vo.setDownloadUrl(signUrl(bucket, objectKey, expire));
                vo.setLocalPath(localFilePath);
                vo.setIsDir(objectKey.endsWith("/"));
                urls.add(vo);

            } catch (Exception e) {
                log.error("download objects err:{}", e);
                throw new BizException("批量下载对象失败：" + e.getMessage());
            }
        }
        return urls;
    }

    @Override
    public void downloadAndZipObjects(String bucket, String remoteFolder, ZipOutputStream zos) throws IOException {
        try {
            // 列出对象
            ListObjectsArgs listObjectsArgs = ListObjectsArgs.builder()
                    .bucket(bucket)
                    .prefix(remoteFolder)
                    .recursive(true) // 递归列出所有对象
                    .build();

            Iterable<Result<Item>> results = minioClient.listObjects(listObjectsArgs);

            for (Result<Item> result : results) {
                Item item = result.get();
                String objectKey = item.objectName();

                if (!objectKey.endsWith("/")) { // 跳过文件夹
                    addFileToZip(bucket, objectKey, zos);
                }
            }
        } catch (Exception e) {
            log.error("download objects in zip err:{}", e);
            throw new BindException("批量下载对象失败：" + e.getMessage());
        }
    }

    private void addFileToZip(String bucket,String objectKey, ZipOutputStream zos) throws Exception {
        // 读取对象并写入 ZIP
        InputStream inputStream = minioClient.getObject(GetObjectArgs.builder()
                .bucket(bucket)
                .object(objectKey)
                .build());

        ZipEntry zipEntry = new ZipEntry(objectKey);
        zos.putNextEntry(zipEntry);

        byte[] buffer = new byte[1024];
        int length;
        while ((length = inputStream.read(buffer)) >= 0) {
            zos.write(buffer, 0, length);
        }
        zos.closeEntry();
    }
}
